package ao.ai.classify.linear;

import ao.ai.ml.model.algo.OnlineBinaryLearner;
import ao.ai.ml.model.input.RealList;
import ao.ai.ml.model.output.BinaryClass;
import ao.ai.ml.model.theory.BinaryScoreClassifier;
import com.google.common.base.Preconditions;

import static ao.ai.classify.linear.LinearClassificationUtilities.sgn;

/**
 * User: AO
 * Date: Oct 9, 2010
 * Time: 3:07:17 PM
 */
public abstract class LinearBinaryOnlineLearner
        implements OnlineBinaryLearner<RealList>
{
    //-------------------------------------------------------------------------
    private double[]                        weights;
    private BinaryScoreClassifier<RealList> classifier =
             new BinaryScoreClassifier.Dummy<RealList>();


    //-------------------------------------------------------------------------
    @Override
    public void learn(RealList input, BinaryClass output)
    {
        Preconditions.checkNotNull(input);
        Preconditions.checkNotNull(output);
        if (weights == null) {
            weights = new double[ input.size() ];
        }

        double positiveScore =
                classifier.classify( input ).positiveScore();

        double observedLabel = sgn( output.isPositive() );

        double[] updateDelta = updateDelta(
                weights, input, positiveScore, observedLabel);

        if (updateDelta != null)
        {
            Preconditions.checkState(
                    updateDelta.length == weights.length);

            for (int i = 0; i < weights.length; i++)
            {
                weights[ i ] += updateDelta[ i ];
            }

            classifier = new LinearBinaryClassifier( weights );
        }
    }

    protected abstract double[] updateDelta(
            double[] previousWeights,
            RealList input,
            double   positiveScore,
            double   observedLabel);


    //-------------------------------------------------------------------------
    @Override
    public BinaryClass classify(RealList input)
    {
        return classifier.classify( input );
    }


    //-------------------------------------------------------------------------
    protected static double[] increaseMarginDelta(
            double[] previousWeights,
            RealList input,
            double   observedLabel,
            double   eta)
    {
        double[] deltas = new double[ previousWeights.length ];
        for (int i = 0; i < deltas.length; i++)
        {
            deltas[ i ] = eta * observedLabel * input.get( i );
        }
        return deltas;
    }
}
